package com.samtech.jackson;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;



@JsonSerialize(using= MorphedInnerJsonSerializer.class)
public class MorphedResult<P> {

	private  P primaryResult;//@JsonFilter(FilteredResultProvider.INNER_FILTER_ID)
	
	private  Set<String> allowedAttributes;  //null implies allow all
	private  Set<String> excludedAttributes; //null implies exclude none
	
	private Map<String, Object> expansionData;  //null implies no expansion data


	public MorphedResult(P primaryResult) {
		assert(primaryResult != null);
		this.primaryResult = primaryResult;
		//Jackson doesn't handle stacked unwrapping and filtering very well.  We must collapse the stack.
		if (primaryResult instanceof MorphedResult) {
			@SuppressWarnings("unchecked")
			MorphedResult<P> other = (MorphedResult<P>) primaryResult;
			this.primaryResult = other.primaryResult;
			//clone collections to prevent write through
			this.allowedAttributes = other.allowedAttributes == null ? null : new HashSet<String>(other.allowedAttributes);
			this.excludedAttributes = other.excludedAttributes == null ? null : new HashSet<String>(other.excludedAttributes);
			
			this.expansionData = other.expansionData == null ? null : new HashMap<String, Object>(other.expansionData);
		}
	}
	
	//@JsonUnwrapped
	 
	public P getPrimaryResult() {
		return primaryResult;
	}

	/**
	 * Returns the set, possibly null, of attribute names that are explicitly declared as allowed.
	 * A null set implies that all properties are implicitly allowed.  Note, even if a property is
	 * declared as allowed, it may also be declared as excluded.  In this conflicting case, this 
	 * implementation will exclude the property.
	 */
	@JsonIgnore
	public Set<String> getAllowedAttributes() {
		return allowedAttributes;
	}
	
	/**
	 * Add the passed in property name to the allowed list.
	 */
	public void allowAttribute(String attributeName) {
		if (allowedAttributes == null) allowedAttributes = new HashSet<String>();
		allowedAttributes.add(attributeName);
	}
	
	/**
	 * Add the passed in property names to the allowed list.
	 */
	public void allowAttributes(String... attributeNames) {
		if (allowedAttributes == null) allowedAttributes = new HashSet<String>();
		allowedAttributes.addAll(Arrays.asList(attributeNames));
	}
	
	/**
	 * Add the passed in property names to the allowed list.
	 */
	public void allowAttributes(Collection<String> attributeNames) {
		if (allowedAttributes == null) allowedAttributes = new HashSet<String>();
		allowedAttributes.addAll(attributeNames);
	}
	
	/**
	 * Returns the set, possibly null, of explicitly excluded attribute names.
	 * If null, then no attributes are explicitly excluded.  However, if an
	 * allowed set exists, then only properties in the allowed list and not the
	 * exclude list will be serialized.
	 */
	@JsonIgnore
	public Set<String> getExcludedAttributes() {
		return excludedAttributes;
	}
	
	/**
	 * Add the passed in property name to the exclude list.
	 */
	public void excludeAttribute(String attributeName) {
		if (excludedAttributes == null) excludedAttributes = new HashSet<String>();
		excludedAttributes.add(attributeName);
	}
	
	/**
	 * Add the passed in property names to the exclude list.
	 */
	public void excludeAttributes(String... attributeNames) {
		if (excludedAttributes == null) excludedAttributes = new HashSet<String>();
		excludedAttributes.addAll(Arrays.asList(attributeNames));
	}
	
	/**
	 * Add the passed in property names to the exclude list.
	 */
	public void excludeAttributes(Collection<String> attributeNames) {
		if (excludedAttributes == null) excludedAttributes = new HashSet<String>();
		excludedAttributes.addAll(attributeNames);
	}
	
	
	
	
	
	/**
	 * Returns true if and only if the specified attribute name will be serialized.
	 * See code for logic.
	 */
	
	public boolean isPermitted(String attribute) {
		boolean permitted = (allowedAttributes == null || allowedAttributes.contains(attribute))
				&& (excludedAttributes == null || !excludedAttributes.contains(attribute));
		return permitted;
	}
	
	
	
	
	/**
	 * Returns a map of attribute names (key) to objects (value) as specified by
	 * Jackson's {@link JsonAnyGetter} annotation.  This method is where the
	 * expanded attributes are exposed to Jackson for serialization.
	 * Note, expanded attributes must be permitted (see isPermitted) according to
	 * the allowed and excluded configuration.
	 */
	
	public Map<String, ?> getExpansionData() {
		if (expansionData == null) {
			return Collections.<String, Object>emptyMap();
		}
		else {
			Iterator<Entry<String, Object>> expansionIterator = expansionData.entrySet().iterator();
			while (expansionIterator.hasNext()) {
				Entry<String, Object> expansionEntry = expansionIterator.next();
				if (!isPermitted(expansionEntry.getKey())) expansionIterator.remove();
			}
			return expansionData;
		}
	}
	
	/**
	 * Add the specified data as an attribute under the specified name to the primary
	 * result, as if it were a property on the primary result.
	 */
	public void addExpansionData(String key, Object data) {
		if (expansionData == null) expansionData = new HashMap<String,Object>();
		expansionData.put(key, data);
	}
	
	
	@Override
	public String toString() {
		return "primaryResult: "+primaryResult
				+"\n"+"allowedAttributes: "+allowedAttributes
				+"\n"+"excludedAttributes: "+excludedAttributes
				
				+"\n"+"expansionData: "+expansionData;
	}
}
